package com.wry.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.util.CharsetUtil;

import java.nio.charset.Charset;

/**
 * <h1> 服务端 <h1>
 *
 * @author wry
 * @since 2023/4/20 14:08
 */
public class NettyServer {
   /** 大致流程：
            ①先创建两个EventLoopGroup事件组，然后创建一个ServerBootstrap服务端。
            ②将创建的两个事件组boss、worker绑定在服务端上，并指定服务端通道为NIO类型。
            ③在server上添加处理器，对新到来的Socket连接进行处理，在这里主要分为两类：
    ChannelInitializer：连接到来时执行，主要是用于添加更多的处理器(只触发一次)。
    addLast()：通过该方式添加的处理器不会立马执行，而是根据处理器类型择机执行。

            ④为创建好的服务端绑定IP及端口号，调用sync()意思是阻塞至绑定成功为止。
            ⑤再创建一个EventLoopGroup事件组，并创建一个Bootstrap客户端。
            ⑥将事件组绑定在客户端上，由于无需处理连接事件，所以只需要一个事件组。
            ⑦指定Channel通道类型为NIO、添加处理器.....（同服务端类似）
            ⑧与前面服务端绑定的地址建立连接，由于默认是异步的，也要调用sync()阻塞。
            ⑨建立连接后，客户端将数据写入到通道准备发送，首先会先经过添加好的编码处理器，将数据的格式设为UTF-8。
            ⑩服务器收到数据后，会先经过解码处理器，然后再去到入站处理，执行对应的Read()方法逻辑。
            ⑪客户端完成数据发送后，先关闭通道，再优雅关闭创建好的事件组。
            ⑫同理，服务端工作完成后，先关闭通道再停止事件组。*/
public static void main(String[] args) throws InterruptedException {
    // 创建两个EventLoopGroup，boss：处理连接事件，worker处理I/O事件
    EventLoopGroup boss = new NioEventLoopGroup();
    EventLoopGroup worker = new NioEventLoopGroup();
    // 创建一个ServerBootstrap服务端（同之前的ServerSocket类似）
    ServerBootstrap server = new ServerBootstrap();
    try {
        // 将前面创建的两个EventLoopGroup绑定在server上
        server.group(boss,worker)
                // 指定服务端的通道为Nio类型
                .channel(NioServerSocketChannel.class)
                // 为到来的客户端Socket添加处理器
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    // 这个只会执行一次（主要是用于添加更多的处理器）
                    @Override
                    protected void initChannel(NioSocketChannel ch) {
                        // 添加一个字符解码处理器：对客户端的数据解码
                        ch.pipeline().addLast(
                                new StringDecoder(CharsetUtil.UTF_8));
                        // 添加一个入站处理器，对收到的数据进行处理
                        ch.pipeline().addLast(
                                new SimpleChannelInboundHandler<String>() {
                                    // 读取事件的回调方法
                                    @Override
                                    protected void channelRead0(ChannelHandlerContext
                                                                        ctx,String msg) {
                                        System.out.println("收到客户端信息：" + msg);
                                    }
                                });
                    }
                });
        // 为当前服务端绑定IP与端口地址(sync是同步阻塞至连接成功为止)
        ChannelFuture cf = server.bind("127.0.0.1",8888).sync();
        // 关闭服务端的方法（之后不会在这里关闭）
        cf.channel().closeFuture().sync();
    }finally {
        // 优雅停止之前创建的两个Group
        boss.shutdownGracefully();
        worker.shutdownGracefully();
    }
}

}
